#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>

// 最多支持连接数目 包括正在连接的和已经连接的
#define MAX_CLIENT_NUM 100

/* 多线程并发服务器 功能:五子棋游戏服务器 可提供多人游戏服务 */

// 线程参数结构体
typedef struct 
{
    int cfd[2];
    char ip_str[2][16];
    unsigned short port[2];
}CLI;

// 全局变量
int lfd = 0;    // 监听套接字
int log_fd = 0;    // 日志文件描述符

// 获取系统时间
int get_time_ms(char*buf, int len)
{
    struct timeval tv;
    struct tm* ptm;

    if(buf == NULL)
    {
        printf("%s buf is NULL\n", __func__);
    }

    gettimeofday(&tv, NULL);

    ptm = localtime(&(tv.tv_sec));

    strftime(buf, len, "%Y-%m-%d %H:%M:%S", ptm);
    return 0;    
}

// 信号注册函数 在程序结束时关闭监听套接字
void exit_fun(int sig)
{
    char str_log[256] = "";
    char time_buf[40] = "";
    // 获取系统时间
    get_time_ms(time_buf, sizeof(time_buf));
    sprintf(str_log, "[%s]服务器关闭\n", time_buf);
    write(log_fd, str_log, strlen(str_log));
    close(lfd); // 关闭套接字
    close(log_fd); // 关闭日志文件    
    _exit(-1);
}

// 线程处理函数 接收cfd[0]客户端消息并转发给cfd[1]客户端
void* deal_client_fun(void* arg)
{
    // 获取参数结构体
    CLI msg = *(CLI*)arg;

    // 服务两个客户端
    while(1)
    {
        // 用于保存返回值 并判断
        int ret = 0;
        // 获取系统时间
        char time_buf[40] = "";        

        // 创建缓冲区
        unsigned char buf[1500] = "";

        // 接收数据
        ret = recv(msg.cfd[0], buf, sizeof(buf), 0);
        if(ret <= 0)    // 套接字已关闭 或者即将关闭
        {
            // 获取系统时间
            get_time_ms(time_buf, sizeof(time_buf));
            // 保存日志信息
            char str_log[256] = "";
            sprintf(str_log, "[%s]用户 %s:%hu已断开连接\n", time_buf, msg.ip_str[0], msg.port[0]);
            write(log_fd, str_log, strlen(str_log));

            // 通知另一个客户端关闭
            send(msg.cfd[1], "CLOSE", strlen("CLOSE"), 0);

            // 关闭双方连接套接字            
            close(msg.cfd[0]);
            close(msg.cfd[1]);
            break;
        }

	// 成功接收到数据buf 存入日志信息
	char str_log[2048] = "";
	get_time_ms(time_buf, sizeof(time_buf));
	sprintf(str_log, "[%s]用户 %s:%hu 发送 %s 给用户 %s:%hu\n", time_buf, msg.ip_str[0], msg.port[0],\
			buf, msg.ip_str[1], msg.port[1]);
	write(log_fd, str_log, strlen(str_log));

        // 把接收到数据buf 转发给另一个客户端
        ret = send(msg.cfd[1], buf, strlen(buf), 0); 
        if(ret <= 0)    // 套接字已关闭 或者即将关闭
        {
            // 获取系统时间
            get_time_ms(time_buf, sizeof(time_buf));
            // 保存日志信息
            char str_log[256] = "";
            sprintf(str_log, "[%s]用户 %s:%hu已断开连接\n", time_buf, msg.ip_str[1], msg.port[1]);
            write(log_fd, str_log, strlen(str_log));


            // 关闭双方连接套接字            
            close(msg.cfd[0]);
            close(msg.cfd[1]);
            break;
        }
    }

    // 回收空间
    free(arg);

    // 结束退出
    pthread_exit(NULL);
    return NULL;
}

// 服务器功能函数
int server()
{
    // 创建监听套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd < 0)
    {
        perror("socket");
        return 0;
    }

    // 设置端口复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 构建自身地址
    struct sockaddr_in my_addr;
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;               // 协议族
    my_addr.sin_port = htons(8000);             // 端口
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);// ip

    // 绑定地址到监听套接字
    int ret = bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    if(ret < 0)
    {
        perror("bind");
        _exit(-1);
    }

    // 进行监听 设置最大连接
    listen(lfd, MAX_CLIENT_NUM);
    char str_log[256] = "";
    char time_buf[40] = "";
    // 获取系统时间
    get_time_ms(time_buf, sizeof(time_buf));
    sprintf(str_log, "[%s]服务器开启 监听端口8000\n", time_buf);
    write(log_fd, str_log, strlen(str_log));

    // 注册信号处理函数
    signal(SIGINT, exit_fun);

    // 声明客户端地址结构体 和 结构体长度变量
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);    

    // 循环提取客户端
    CLI arg;    // 暂存连接客户端的套接字和客户端的地址信息
    memset(&arg, 0 ,sizeof(arg));
    while(1)
    {
        int tmp_cfd = accept(lfd, (struct sockaddr*)&client_addr, &client_len);
        if(tmp_cfd < 0)
        {
            if((errno == ECONNABORTED) || (errno == EINTR))
            {
                continue;
            }
            perror("accept");
        }
        else 
        {   
            // 获取系统时间
            char time_buf[40] = "";
            get_time_ms(time_buf, sizeof(time_buf));

            if(arg.cfd[0] == 0)         // 提取到0号客户端
            {
                // 保存0号客户端套接字 和 地址信息
                arg.cfd[0] = tmp_cfd;
                inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, arg.ip_str[0], 16);
                arg.port[0] = ntohs(client_addr.sin_port);                

                // 保存日志信息
                char str_log[256] = "";
                sprintf(str_log, "[%s]用户 %s:%hu连接到来\n", time_buf, arg.ip_str[0], arg.port[0]);
                write(log_fd, str_log, strlen(str_log));
            }
            else                    // 两个客户端都已经提取到
            {
                // 保存1号客户端套接字 和 地址信息
                arg.cfd[1] = tmp_cfd;
                inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, arg.ip_str[1], 16);
                arg.port[1] = ntohs(client_addr.sin_port);

                // 保存日志信息
                char str_log[256] = "";
                sprintf(str_log, "[%s]用户 %s:%hu连接到来\n", time_buf, arg.ip_str[1], arg.port[1]);
                write(log_fd, str_log, strlen(str_log));

                // 开启线程之前 通知两个客户端 可以开始游戏 同时分配棋子类型 同时验证两个客户端是否都还保持连接
                int ret = 0;
                // 验证0号客户端是否还保持连接
                ret = send(arg.cfd[0], "TYPE(1)", strlen("TYPE(1)"), 0);
                if(ret <= 0)    // 0号客户端已经断开连接
                {
                    // 保存日志信息
                    char str_log[256] = "";
                    sprintf(str_log, "[%s]用户 %s:%hu连接提前断开 0号客户端 ret=%d\n", time_buf, arg.ip_str[0], arg.port[0], ret);
                    write(log_fd, str_log, strlen(str_log));

                    // 将1号客户端作为0号客户端继续等待
                    arg.cfd[0] = arg.cfd[1];
                    arg.port[0] = arg.port[1];
                    strcpy(arg.ip_str[0], arg.ip_str[1]);
                    continue;
                }
                else            // 0号客户端还保持着连接 
                {
                    // 验证1号客户端是否还保持连接
                    ret = send(arg.cfd[1], "TYPE(-1)", strlen("TYPE(-1)"), 0);
                    if(ret <= 0)    // 1号客户端已经断开连接
                    {
                        // 保存日志信息
                        char str_log[256] = "";
                        sprintf(str_log, "[%s]用户 %s:%hu连接提前断开 1号客户端\n", time_buf, arg.ip_str[1], arg.port[1]);
                        write(log_fd, str_log, strlen(str_log));
                        continue;
                    }
                    else            // 两个客户端都正常连接 则开始创建线程服务两个客户端
                    {
                        // 创建线程 进行处理
                        pthread_t tid_0, tid_1;    
                        // 0号线程 参数设置 
                        CLI *arg_1 = malloc(sizeof(CLI));       // 临时申请的空间会在线程内部释放
                        arg_1->cfd[0] = arg.cfd[0];
                        arg_1->cfd[1] = arg.cfd[1];
                        arg_1->port[0] = arg.port[0];
                        arg_1->port[1] = arg.port[1];
                        strcpy(arg_1->ip_str[0], arg.ip_str[0]);
                        strcpy(arg_1->ip_str[1], arg.ip_str[1]);

                        pthread_create(&tid_0, NULL, deal_client_fun, (void*)arg_1);    // 0号线程负责从 cfd[0] 向cfd[1]发送信息

                        // 1号线程 参数设置
                        CLI *arg_2 = malloc(sizeof(CLI));       // 临时申请的空间会在线程内部释放
                        arg_2->cfd[0] = arg.cfd[1];
                        arg_2->cfd[1] = arg.cfd[0];
                        arg_2->port[0] = arg.port[1];
                        arg_2->port[1] = arg.port[0];
                        strcpy(arg_2->ip_str[0], arg.ip_str[1]);
                        strcpy(arg_2->ip_str[1], arg.ip_str[0]);

                        pthread_create(&tid_1, NULL, deal_client_fun, (void*)arg_2);    // 1号线程负责从 cfd[1] 向cfd[0]发送信息
                        pthread_detach(tid_0);
                        pthread_detach(tid_1);

                        // 重置参数
                        memset(&arg, 0, sizeof(arg));
                        memset(&client_addr, 0, sizeof(client_addr));
                    }
                }                
            }
        }        
    }
    return 0;
}

int main(int argc, char const *argv[])
{   
    // 创建守护进程
    pid_t pid = fork();
    if(pid > 0)
        _exit(0);       // 结束父进程
    
    setsid();           // 子进程设置会话
    chdir("/home/ubuntu/server/wuziqi_server/");
    umask(0002);        // 设置权限掩码

    // 打开日志文件
    log_fd = open("./log", O_WRONLY | O_APPEND);
    close(0);
    close(1);
    close(2);

    // 启动服务器
    server();

    return 0;
}
